/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.belfalas.simy86;

import java.util.logging.Level;
import java.util.logging.Logger;
import org.belfalas.simy86.operadores.*;

/**
 *
 * @author belfalas
 */
public class Y86 {

    public static Memoria memoria;
    public static boolean cycle;
    private static Operador op[] = new Operador[5];
    private static Operador_Stat[] stats = new Operador_Stat[5];
    private int pc;
    public static String tab = "   ";
    public static int tab_numbers = 0;
    private static long delay =  0;
    private static boolean delayIssued = false;
    private int cycleCount = 0;

    public Y86() {
        memoria = new Memoria((1 << 20) * 2);
        for (int i = 0; i < 5; i++) {
            op[i] = new NOP();
            stats[i] = new Operador_Stat();
            stats[i].reset();
        }
    }

    public void cycle() {
        cycle = true;
        while (cycle) {
            flux();
        }
    }
    
    public void issueDelay(long millis){
        delayIssued = true;
        delay = millis;
    }
    
    private int retCounter = 0;
    private int haltCounter = 0;

    private void flux() {
        short code;
        if (!(retCounter > 0 && retCounter < 4) && !(haltCounter > 0 && haltCounter < 5)) {
            if (retCounter >= 4) {
                pc = stats[4].valM;
            }
            retCounter = 0;
            haltCounter = 0;
            code = memoria.readUnsignedByte(pc);
            op[0] = OperadorFactory.getOperador(code);
            stats[0].pc = pc;
        } else {
            insertBubble(0);
            stats[0].predicted_pc = pc;
        }

        op[4].writeback(stats[4]);
        op[3].memory(stats[3]);
        op[2].execute(stats[2]);
        if (checkLoadUseHazard()) {
            insertBubble(1);
            stats[1].predicted_pc = stats[0].predicted_pc;
        }
        op[1].decode(stats[1]);

        if (checkNeedDataFoward(stats[1])) {
            if (stats[1].rA != null && stats[1].rA.getUsage() > 1) {
                int a = dataFoward(stats[1].rA);
                if (a != 0) {
                    stats[1].valA = a;
                }
            }
            int b = dataFoward(stats[1].rB);
            if (b != 0) {
                stats[1].valB = b;
            }
        }

        op[0].fetch(stats[0]);

        if (op[4] instanceof HALT) {
            haltCounter++;
        }

        for (int i = 4; i > 0; i--) {
            if (op[i - 1] instanceof RET) {
                retCounter++;
            }

            if (op[i - 1] instanceof HALT) {
                haltCounter++;
                stats[0].predicted_pc = pc;
            }
            op[i] = op[i - 1];
            stats[i] = stats[i - 1];
        }
        op[0] = new NOP();

        stats[0] = new Operador_Stat();
        stats[0].reset();
        pc = stats[1].predicted_pc;

        if (op[3] instanceof JXX && !stats[3].Cnd) {
            jumpRecovery();
            pc = stats[3].valP;
        }

    }

    public void cycle(int n) {
        cycle = true;
        for (int i = 0; (i < n) && cycle; i++) {
            flux();
        }
    }

    public void setPC(int n) {
        pc = n;
    }

    public int getPC() {
        return pc;
    }

    private void insertBubble(int stage) {
        for (int i = stage; i > 0; i--) {
            op[i - 1] = op[i];
            stats[i - 1] = stats[i];
        }
        if (op[stage] instanceof RET) {
            retCounter--;
        }
        if (op[stage] instanceof HALT) {
            haltCounter--;
        }
        if (stage == 1) {
            if (stats[1].rA != null) {
                stats[1].rA.decUsage();
            }
            if (stats[1].rB != null) {
                stats[1].rB.decUsage();
            }
        }
        op[stage] = new NOP();
        stats[stage] = new Operador_Stat();
        stats[stage].reset();
    }

    private boolean checkNeedDataFoward(Operador_Stat stat) {
        if ((stat.rA != null && (stat.rA.getUsage() > 1)) || (stat.rB != null && (stat.rB.getUsage() > 1))) {
            return true;
        } else {
            return false;
        }
    }

    private int dataFoward(Registradores stat) {

        if (stats[2].rB != null && stats[2].rB == stat) {
            return stats[2].valE;
        }

        if (stats[3].rB != null && stats[3].rB == stat) {
            return stats[3].valE;
        }

        if (stats[3].rA != null && stats[3].rA == stat) {
            return stats[3].valM;
        }

        if (stats[4].rB != null && stats[4].rB == stat) {
            return stats[4].valE;
        }

        if (stats[4].rA != null && stats[4].rA == stat) {
            return stats[4].valM;
        }

        return 0;
    }

    private boolean checkLoadUseHazard() {
        if (op[2] instanceof MRMOVL || op[2] instanceof POPL) {
            if (stats[2].rA == stats[1].rA || stats[2].rA == stats[1].rB
                    || stats[2].rB == stats[1].rA || stats[2].rB == stats[1].rB) {
                return true;
            }
        }
        return false;
    }

    public void jumpRecovery() {
        stats[0].pc = stats[3].valC;
        if (stats[1].rA != null) {
            stats[1].rA.decUsage();
        }
        if (stats[1].rB != null) {
            stats[1].rB.decUsage();
        }
        if (stats[2].rA != null) {
            stats[2].rA.decUsage();
        }
        if (stats[2].rB != null) {
            stats[2].rB.decUsage();
        }
        op[1] = new NOP();
        op[2] = new NOP();
    }
}
